home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / utils / src / tar-1.11.2.shar / diffarch.c < prev    next >
C/C++ Source or Header  |  1994-01-23  |  17KB  |  760 lines

  1. /* Diff files from a tar archive.
  2.    Copyright (C) 1988, 1992, 1993 Free Software Foundation
  3.  
  4. This file is part of GNU Tar.
  5.  
  6. GNU Tar is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10.  
  11. GNU Tar is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU Tar; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /*
  21.  * Diff files from a tar archive.
  22.  *
  23.  * Written 30 April 1987 by John Gilmore, ihnp4!hoptoad!gnu.
  24.  */
  25.  
  26. #include <stdio.h>
  27. #include <errno.h>
  28. #ifndef STDC_HEADERS
  29. extern int errno;
  30. #endif
  31. #include <sys/types.h>
  32.  
  33. #ifdef BSD42
  34. #include <sys/file.h>
  35. #else
  36. #ifndef V7
  37. #include <fcntl.h>
  38. #endif
  39. #endif
  40.  
  41. #ifdef HAVE_SYS_MTIO_H
  42. #include <sys/ioctl.h>
  43. #include <sys/mtio.h>
  44. #endif
  45.  
  46. #include "tar.h"
  47. #include "port.h"
  48. #include "rmt.h"
  49.  
  50. #ifndef S_ISLNK
  51. #define lstat stat
  52. #endif
  53.  
  54. extern void *valloc ();
  55.  
  56. extern union record *head;    /* Points to current tape header */
  57. extern struct stat hstat;    /* Stat struct corresponding */
  58. extern int head_standard;    /* Tape header is in ANSI format */
  59.  
  60. void decode_header ();
  61. void diff_sparse_files ();
  62. void fill_in_sparse_array ();
  63. void fl_read ();
  64. long from_oct ();
  65. int do_stat ();
  66. extern void print_header ();
  67. int read_header ();
  68. void saverec ();
  69. void sigh ();
  70. extern void skip_file ();
  71. extern void skip_extended_headers ();
  72. int wantbytes ();
  73.  
  74. extern FILE *msg_file;
  75.  
  76. int now_verifying = 0;        /* Are we verifying at the moment? */
  77.  
  78. int diff_fd;            /* Descriptor of file we're diffing */
  79.  
  80. char *diff_buf = 0;        /* Pointer to area for reading
  81.                        file contents into */
  82.  
  83. char *diff_dir;            /* Directory contents for LF_DUMPDIR */
  84.  
  85. int different = 0;
  86.  
  87. /*struct sp_array *sparsearray;
  88. int         sp_ar_size = 10;*/
  89. /*
  90.  * Initialize for a diff operation
  91.  */
  92. void
  93. diff_init ()
  94. {
  95.   /*NOSTRICT*/
  96.   diff_buf = (char *) valloc ((unsigned) blocksize);
  97.   if (!diff_buf)
  98.     {
  99.       msg ("could not allocate memory for diff buffer of %d bytes",
  100.        blocksize);
  101.       exit (EX_ARGSBAD);
  102.     }
  103. }
  104.  
  105. /*
  106.  * Diff a file against the archive.
  107.  */
  108. void
  109. diff_archive ()
  110. {
  111.   register char *data;
  112.   int check, namelen;
  113.   int err;
  114.   long offset;
  115.   struct stat filestat;
  116.   int compare_chunk ();
  117.   int compare_dir ();
  118.   int no_op ();
  119. #ifndef __MSDOS__
  120.   dev_t dev;
  121.   ino_t ino;
  122. #endif
  123.   char *get_dir_contents ();
  124.   long from_oct ();
  125.  
  126.   errno = EPIPE;        /* FIXME, remove perrors */
  127.  
  128.   saverec (&head);        /* Make sure it sticks around */
  129.   userec (head);        /* And go past it in the archive */
  130.   decode_header (head, &hstat, &head_standard, 1);    /* Snarf fields */
  131.  
  132.   /* Print the record from 'head' and 'hstat' */
  133.   if (f_verbose)
  134.     {
  135.       if (now_verifying)
  136.     fprintf (msg_file, "Verify ");
  137.       print_header ();
  138.     }
  139.  
  140.   switch (head->header.linkflag)
  141.     {
  142.  
  143.     default:
  144.       msg ("Unknown file type '%c' for %s, diffed as normal file",
  145.        head->header.linkflag, current_file_name);
  146.       /* FALL THRU */
  147.  
  148.     case LF_OLDNORMAL:
  149.     case LF_NORMAL:
  150.     case LF_SPARSE:
  151.     case LF_CONTIG:
  152.       /*
  153.          * Appears to be a file.
  154.          * See if it's really a directory.
  155.          */
  156.       namelen = strlen (current_file_name) - 1;
  157.       if (current_file_name[namelen] == '/')
  158.     goto really_dir;
  159.  
  160.  
  161.       if (do_stat (&filestat))
  162.     {
  163.       if (head->header.isextended)
  164.         skip_extended_headers ();
  165.       skip_file ((long) hstat.st_size);
  166.       different++;
  167.       goto quit;
  168.     }
  169.  
  170.       if (!S_ISREG (filestat.st_mode))
  171.     {
  172.       fprintf (msg_file, "%s: not a regular file\n",
  173.            current_file_name);
  174.       skip_file ((long) hstat.st_size);
  175.       different++;
  176.       goto quit;
  177.     }
  178.  
  179.       filestat.st_mode &= 07777;
  180.       if (filestat.st_mode != hstat.st_mode)
  181.     sigh ("mode");
  182.       if (filestat.st_uid != hstat.st_uid)
  183.     sigh ("uid");
  184.       if (filestat.st_gid != hstat.st_gid)
  185.     sigh ("gid");
  186.       if (filestat.st_mtime != hstat.st_mtime)
  187.     sigh ("mod time");
  188.       if (head->header.linkflag != LF_SPARSE &&
  189.       filestat.st_size != hstat.st_size)
  190.     {
  191.       sigh ("size");
  192.       skip_file ((long) hstat.st_size);
  193.       goto quit;
  194.     }
  195.  
  196.       diff_fd = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
  197.  
  198.       if (diff_fd < 0 && !f_absolute_paths)
  199.     {
  200.       char tmpbuf[NAMSIZ + 2];
  201.  
  202.       tmpbuf[0] = '/';
  203.       strcpy (&tmpbuf[1], current_file_name);
  204.       diff_fd = open (tmpbuf, O_NDELAY | O_RDONLY);
  205.     }
  206.       if (diff_fd < 0)
  207.     {
  208.       msg_perror ("cannot open %s", current_file_name);
  209.       if (head->header.isextended)
  210.         skip_extended_headers ();
  211.       skip_file ((long) hstat.st_size);
  212.       different++;
  213.       goto quit;
  214.     }
  215.       /*
  216.          * Need to treat sparse files completely differently here.
  217.          */
  218.       if (head->header.linkflag == LF_SPARSE)
  219.     diff_sparse_files (hstat.st_size);
  220.       else
  221.     wantbytes ((long) (hstat.st_size), compare_chunk);
  222.  
  223.       check = close (diff_fd);
  224.       if (check < 0)
  225.     msg_perror ("Error while closing %s", current_file_name);
  226.  
  227.     quit:
  228.       break;
  229.  
  230. #ifndef __MSDOS__
  231.     case LF_LINK:
  232.       if (do_stat (&filestat))
  233.     break;
  234.       dev = filestat.st_dev;
  235.       ino = filestat.st_ino;
  236.       err = stat (current_link_name, &filestat);
  237.       if (err < 0)
  238.     {
  239.       if (errno == ENOENT)
  240.         {
  241.           fprintf (msg_file, "%s: does not exist\n", current_file_name);
  242.         }
  243.       else
  244.         {
  245.           msg_perror ("cannot stat file %s", current_file_name);
  246.         }
  247.       different++;
  248.       break;
  249.     }
  250.       if (filestat.st_dev != dev || filestat.st_ino != ino)
  251.     {
  252.       fprintf (msg_file, "%s not linked to %s\n", current_file_name, current_link_name);
  253.       break;
  254.     }
  255.       break;
  256. #endif
  257.  
  258. #ifdef S_ISLNK
  259.     case LF_SYMLINK:
  260.       {
  261.     char linkbuf[NAMSIZ + 3];
  262.     check = readlink (current_file_name, linkbuf,
  263.               (sizeof linkbuf) - 1);
  264.  
  265.     if (check < 0)
  266.       {
  267.         if (errno == ENOENT)
  268.           {
  269.         fprintf (msg_file,
  270.              "%s: no such file or directory\n",
  271.              current_file_name);
  272.           }
  273.         else
  274.           {
  275.         msg_perror ("cannot read link %s", current_file_name);
  276.           }
  277.         different++;
  278.         break;
  279.       }
  280.  
  281.     linkbuf[check] = '\0';    /* Null-terminate it */
  282.     if (strncmp (current_link_name, linkbuf, check) != 0)
  283.       {
  284.         fprintf (msg_file, "%s: symlink differs\n",
  285.              current_link_name);
  286.         different++;
  287.       }
  288.       }
  289.       break;
  290. #endif
  291.  
  292. #ifdef S_IFCHR
  293.     case LF_CHR:
  294.       hstat.st_mode |= S_IFCHR;
  295.       goto check_node;
  296. #endif
  297.  
  298. #ifdef S_IFBLK
  299.       /* If local system doesn't support block devices, use default case */
  300.     case LF_BLK:
  301.       hstat.st_mode |= S_IFBLK;
  302.       goto check_node;
  303. #endif
  304.  
  305. #ifdef S_ISFIFO
  306.       /* If local system doesn't support FIFOs, use default case */
  307.     case LF_FIFO:
  308. #ifdef S_IFIFO
  309.       hstat.st_mode |= S_IFIFO;
  310. #endif
  311.       hstat.st_rdev = 0;    /* FIXME, do we need this? */
  312.       goto check_node;
  313. #endif
  314.  
  315.     check_node:
  316.       /* FIXME, deal with umask */
  317.       if (do_stat (&filestat))
  318.     break;
  319.       if (hstat.st_rdev != filestat.st_rdev)
  320.     {
  321.       fprintf (msg_file, "%s: device numbers changed\n", current_file_name);
  322.       different++;
  323.       break;
  324.     }
  325. #ifdef S_IFMT
  326.       if (hstat.st_mode != filestat.st_mode)
  327. #else /* POSIX lossage */
  328.       if ((hstat.st_mode & 07777) != (filestat.st_mode & 07777))
  329. #endif
  330.     {
  331.       fprintf (msg_file, "%s: mode or device-type changed\n", current_file_name);
  332.       different++;
  333.       break;
  334.     }
  335.       break;
  336.  
  337.     case LF_DUMPDIR:
  338.       data = diff_dir = get_dir_contents (current_file_name, 0);
  339.       if (data)
  340.     {
  341.       wantbytes ((long) (hstat.st_size), compare_dir);
  342.       free (data);
  343.     }
  344.       else
  345.     wantbytes ((long) (hstat.st_size), no_op);
  346.       /* FALL THROUGH */
  347.  
  348.     case LF_DIR:
  349.       /* Check for trailing / */
  350.       namelen = strlen (current_file_name) - 1;
  351.     really_dir:
  352.       while (namelen && current_file_name[namelen] == '/')
  353.     current_file_name[namelen--] = '\0';    /* Zap / */
  354.  
  355.       if (do_stat (&filestat))
  356.     break;
  357.       if (!S_ISDIR (filestat.st_mode))
  358.     {
  359.       fprintf (msg_file, "%s is no longer a directory\n", current_file_name);
  360.       different++;
  361.       break;
  362.     }
  363.       if ((filestat.st_mode & 07777) != (hstat.st_mode & 07777))
  364.     sigh ("mode");
  365.       break;
  366.  
  367.     case LF_VOLHDR:
  368.       break;
  369.  
  370.     case LF_MULTIVOL:
  371.       namelen = strlen (current_file_name) - 1;
  372.       if (current_file_name[namelen] == '/')
  373.     goto really_dir;
  374.  
  375.       if (do_stat (&filestat))
  376.     break;
  377.  
  378.       if (!S_ISREG (filestat.st_mode))
  379.     {
  380.       fprintf (msg_file, "%s: not a regular file\n",
  381.            current_file_name);
  382.       skip_file ((long) hstat.st_size);
  383.       different++;
  384.       break;
  385.     }
  386.  
  387.       filestat.st_mode &= 07777;
  388.       offset = from_oct (1 + 12, head->header.offset);
  389.       if (filestat.st_size != hstat.st_size + offset)
  390.     {
  391.       sigh ("size");
  392.       skip_file ((long) hstat.st_size);
  393.       different++;
  394.       break;
  395.     }
  396.  
  397.       diff_fd = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
  398.  
  399.       if (diff_fd < 0)
  400.     {
  401.       msg_perror ("cannot open file %s", current_file_name);
  402.       skip_file ((long) hstat.st_size);
  403.       different++;
  404.       break;
  405.     }
  406.       err = lseek (diff_fd, offset, 0);
  407.       if (err != offset)
  408.     {
  409.       msg_perror ("cannot seek to %ld in file %s", offset, current_file_name);
  410.       different++;
  411.       break;
  412.     }
  413.  
  414.       wantbytes ((long) (hstat.st_size), compare_chunk);
  415.  
  416.       check = close (diff_fd);
  417.       if (check < 0)
  418.     {
  419.       msg_perror ("Error while closing %s", current_file_name);
  420.     }
  421.       break;
  422.  
  423.     }
  424.  
  425.   /* We don't need to save it any longer. */
  426.   saverec ((union record **) 0);/* Unsave it */
  427. }
  428.  
  429. int
  430. compare_chunk (bytes, buffer)
  431.      long bytes;
  432.      char *buffer;
  433. {
  434.   int err;
  435.  
  436.   err = read (diff_fd, diff_buf, bytes);
  437.   if (err != bytes)
  438.     {
  439.       if (err < 0)
  440.     {
  441.       msg_perror ("can't read %s", current_file_name);
  442.     }
  443.       else
  444.     {
  445.       fprintf (msg_file, "%s: could only read %d of %d bytes\n", current_file_name, err, bytes);
  446.     }
  447.       different++;
  448.       return -1;
  449.     }
  450.   if (bcmp (buffer, diff_buf, bytes))
  451.     {
  452.       fprintf (msg_file, "%s: data differs\n", current_file_name);
  453.       different++;
  454.       return -1;
  455.     }
  456.   return 0;
  457. }
  458.  
  459. int
  460. compare_dir (bytes, buffer)
  461.      long bytes;
  462.      char *buffer;
  463. {
  464.   if (bcmp (buffer, diff_dir, bytes))
  465.     {
  466.       fprintf (msg_file, "%s: data differs\n", current_file_name);
  467.       different++;
  468.       return -1;
  469.     }
  470.   diff_dir += bytes;
  471.   return 0;
  472. }
  473.  
  474. /*
  475.  * Sigh about something that differs.
  476.  */
  477. void
  478. sigh (what)
  479.      char *what;
  480. {
  481.  
  482.   fprintf (msg_file, "%s: %s differs\n",
  483.        current_file_name, what);
  484. }
  485.  
  486. void
  487. verify_volume ()
  488. {
  489.   int status;
  490. #ifdef MTIOCTOP
  491.   struct mtop t;
  492.   int er;
  493. #endif
  494.  
  495.   if (!diff_buf)
  496.     diff_init ();
  497. #ifdef MTIOCTOP
  498.   t.mt_op = MTBSF;
  499.   t.mt_count = 1;
  500.   if ((er = rmtioctl (archive, MTIOCTOP, &t)) < 0)
  501.     {
  502.       if (errno != EIO || (er = rmtioctl (archive, MTIOCTOP, &t)) < 0)
  503.     {
  504. #endif
  505.       if (rmtlseek (archive, 0L, 0) != 0)
  506.         {
  507.           /* Lseek failed.  Try a different method */
  508.           msg_perror ("Couldn't rewind archive file for verify");
  509.           return;
  510.         }
  511. #ifdef MTIOCTOP
  512.     }
  513.     }
  514. #endif
  515.   ar_reading = 1;
  516.   now_verifying = 1;
  517.   fl_read ();
  518.   for (;;)
  519.     {
  520.       status = read_header ();
  521.       if (status == 0)
  522.     {
  523.       unsigned n;
  524.  
  525.       n = 0;
  526.       do
  527.         {
  528.           n++;
  529.           status = read_header ();
  530.         }
  531.       while (status == 0);
  532.       msg ("VERIFY FAILURE: %d invalid header%s detected!", n, n == 1 ? "" : "s");
  533.     }
  534.       if (status == 2 || status == EOF)
  535.     break;
  536.       diff_archive ();
  537.     }
  538.   ar_reading = 0;
  539.   now_verifying = 0;
  540.  
  541. }
  542.  
  543. int
  544. do_stat (statp)
  545.      struct stat *statp;
  546. {
  547.   int err;
  548.  
  549.   err = f_follow_links ? stat (current_file_name, statp) : lstat (current_file_name, statp);
  550.   if (err < 0)
  551.     {
  552.       if (errno == ENOENT)
  553.     {
  554.       fprintf (msg_file, "%s: does not exist\n", current_file_name);
  555.     }
  556.       else
  557.     msg_perror ("can't stat file %s", current_file_name);
  558.       /*        skip_file((long)hstat.st_size);
  559.         different++;*/
  560.       return 1;
  561.     }
  562.   else
  563.     return 0;
  564. }
  565.  
  566. /*
  567.  * JK
  568.  * Diff'ing a sparse file with its counterpart on the tar file is a
  569.  * bit of a different story than a normal file.  First, we must know
  570.  * what areas of the file to skip through, i.e., we need to contruct
  571.  * a sparsearray, which will hold all the information we need.  We must
  572.  * compare small amounts of data at a time as we find it.
  573.  */
  574.  
  575. void
  576. diff_sparse_files (filesize)
  577.      int filesize;
  578.  
  579. {
  580.   int sparse_ind = 0;
  581.   char *buf;
  582.   int buf_size = RECORDSIZE;
  583.   union record *datarec;
  584.   int err;
  585.   long numbytes;
  586.   /*    int        amt_read = 0;*/
  587.   int size = filesize;
  588.  
  589.   buf = (char *) ck_malloc (buf_size * sizeof (char));
  590.  
  591.   fill_in_sparse_array ();
  592.  
  593.  
  594.   while (size > 0)
  595.     {
  596.       datarec = findrec ();
  597.       if (!sparsearray[sparse_ind].numbytes)
  598.     break;
  599.  
  600.       /*
  601.          * 'numbytes' is nicer to write than
  602.          * 'sparsearray[sparse_ind].numbytes' all the time ...
  603.          */
  604.       numbytes = sparsearray[sparse_ind].numbytes;
  605.  
  606.       lseek (diff_fd, sparsearray[sparse_ind].offset, 0);
  607.       /*
  608.          * take care to not run out of room in our buffer
  609.          */
  610.       while (buf_size < numbytes)
  611.     {
  612.       buf = (char *) ck_realloc (buf, buf_size * 2 * sizeof (char));
  613.       buf_size *= 2;
  614.     }
  615.       while (numbytes > RECORDSIZE)
  616.     {
  617.       if ((err = read (diff_fd, buf, RECORDSIZE)) != RECORDSIZE)
  618.         {
  619.           if (err < 0)
  620.         msg_perror ("can't read %s", current_file_name);
  621.           else
  622.         fprintf (msg_file, "%s: could only read %d of %d bytes\n",
  623.              current_file_name, err, numbytes);
  624.           break;
  625.         }
  626.       if (bcmp (buf, datarec->charptr, RECORDSIZE))
  627.         {
  628.           different++;
  629.           break;
  630.         }
  631.       numbytes -= err;
  632.       size -= err;
  633.       userec (datarec);
  634.       datarec = findrec ();
  635.     }
  636.       if ((err = read (diff_fd, buf, numbytes)) != numbytes)
  637.     {
  638.       if (err < 0)
  639.         msg_perror ("can't read %s", current_file_name);
  640.       else
  641.         fprintf (msg_file, "%s: could only read %d of %d bytes\n",
  642.              current_file_name, err, numbytes);
  643.       break;
  644.     }
  645.  
  646.       if (bcmp (buf, datarec->charptr, numbytes))
  647.     {
  648.       different++;
  649.       break;
  650.     }
  651.       /*        amt_read += numbytes;
  652.         if (amt_read >= RECORDSIZE) {
  653.             amt_read = 0;
  654.             userec(datarec);
  655.             datarec = findrec();
  656.         }*/
  657.       userec (datarec);
  658.       sparse_ind++;
  659.       size -= numbytes;
  660.     }
  661.   /*
  662.      * if the number of bytes read isn't the
  663.      * number of bytes supposedly in the file,
  664.      * they're different
  665.      */
  666.   /*    if (amt_read != filesize)
  667.         different++;*/
  668.   userec (datarec);
  669.   free (sparsearray);
  670.   if (different)
  671.     fprintf (msg_file, "%s: data differs\n", current_file_name);
  672.  
  673. }
  674.  
  675. /*
  676.  * JK
  677.  * This routine should be used more often than it is ... look into
  678.  * that.  Anyhow, what it does is translate the sparse information
  679.  * on the header, and in any subsequent extended headers, into an
  680.  * array of structures with true numbers, as opposed to character
  681.  * strings.  It simply makes our life much easier, doing so many
  682.  * comparisong and such.
  683.  */
  684. void
  685. fill_in_sparse_array ()
  686. {
  687.   int ind;
  688.  
  689.   /*
  690.      * allocate space for our scratch space; it's initially
  691.      * 10 elements long, but can change in this routine if
  692.      * necessary
  693.      */
  694.   sp_array_size = 10;
  695.   sparsearray = (struct sp_array *) ck_malloc (sp_array_size * sizeof (struct sp_array));
  696.  
  697.   /*
  698.      * there are at most five of these structures in the header
  699.      * itself; read these in first
  700.      */
  701.   for (ind = 0; ind < SPARSE_IN_HDR; ind++)
  702.     {
  703.       if (!head->header.sp[ind].numbytes)
  704.     break;
  705.       sparsearray[ind].offset =
  706.     from_oct (1 + 12, head->header.sp[ind].offset);
  707.       sparsearray[ind].numbytes =
  708.     from_oct (1 + 12, head->header.sp[ind].numbytes);
  709.     }
  710.   /*
  711.      * if the header's extended, we gotta read in exhdr's till
  712.      * we're done
  713.      */
  714.   if (head->header.isextended)
  715.     {
  716.       /* how far into the sparsearray we are 'so far' */
  717.       static int so_far_ind = SPARSE_IN_HDR;
  718.       union record *exhdr;
  719.  
  720.       for (;;)
  721.     {
  722.       exhdr = findrec ();
  723.       for (ind = 0; ind < SPARSE_EXT_HDR; ind++)
  724.         {
  725.           if (ind + so_far_ind > sp_array_size - 1)
  726.         {
  727.           /*
  728.                   * we just ran out of room in our
  729.                  *  scratch area - realloc it
  730.                   */
  731.           sparsearray = (struct sp_array *)
  732.             ck_realloc (sparsearray,
  733.                  sp_array_size * 2 * sizeof (struct sp_array));
  734.           sp_array_size *= 2;
  735.         }
  736.           /*
  737.              * convert the character strings into longs
  738.              */
  739.           sparsearray[ind + so_far_ind].offset =
  740.         from_oct (1 + 12, exhdr->ext_hdr.sp[ind].offset);
  741.           sparsearray[ind + so_far_ind].numbytes =
  742.         from_oct (1 + 12, exhdr->ext_hdr.sp[ind].numbytes);
  743.         }
  744.       /*
  745.          * if this is the last extended header for this
  746.          * file, we can stop
  747.          */
  748.       if (!exhdr->ext_hdr.isextended)
  749.         break;
  750.       else
  751.         {
  752.           so_far_ind += SPARSE_EXT_HDR;
  753.           userec (exhdr);
  754.         }
  755.     }
  756.       /* be sure to skip past the last one  */
  757.       userec (exhdr);
  758.     }
  759. }
  760.